using AOT;
using Unity.Burst;
using UnityEngine.Animations;
using UnityEngine.Playables;
using Unity.Mathematics;
using static DA.AnimGraph.TriangulationData;
using AnimCollections;

namespace DA.AnimGraph {
    [BurstCompile]
    public unsafe partial struct BlendSpace2DNode : INode {

        public struct SetupData : INodeSetupData {
            public Vector2LinkDesc positionDesc;
            public TriangulationDataSerializable triangulationData;
            public ClipLinkDesc[] clipsDesc;
        }

        //
        private Vector2Link linkPosition;
        private TriangulationData triangulationData;
        private NativeSimpleArray<float> weights;
        private NativeSimpleArray<AnimationClipPlayable> childPlayables;
        private NativeSimpleArray<ClipLink> linkChildClips;

        //
        private AnimationMixerPlayable playable;
        private TimeState timeState;
        private int lastTriangleIndex;

        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.DisposeDelegate))]
        private static bool Dispose(in void* node) {
            var self = (BlendSpace2DNode*)node;

            for (int i = 0; i < self->childPlayables.Length; i++) {
                self->childPlayables[i].Destroy();
            }
            self->childPlayables.Dispose();

            self->triangulationData.Dispose();
            self->weights.Dispose();
            self->linkChildClips.Dispose();

            return false;
        }

        private void Setup(in SetupData data, ref GraphBuffer buffer, in NodeLink* link) {
            linkPosition = new Vector2Link(data.positionDesc);
            triangulationData = data.triangulationData.ToNative(ref buffer);

            int childrenCount = data.clipsDesc.Length;
            buffer.MallocNativeSimpleArray(out linkChildClips, childrenCount);
            for (int i = 0; i < childrenCount; i++) {
                linkChildClips[i] = new(data.clipsDesc[i]);
            }

            buffer.MallocNativeSimpleArray(out weights, childrenCount);
            buffer.MallocNativeSimpleArray(out childPlayables, childrenCount);
        }

        private static Playable BuildPlayableGraph(in void* node, in NodeLink* link, in PlayableGraph graph, in BuildContext buildContext) {
            var self = (BlendSpace2DNode*)node;

            self->timeState = new(TimeState.AccumulateMode.Normalize, true);
            link->SetTimeState(self->timeState);
            self->lastTriangleIndex = -1;

            int childrenCount = self->linkChildClips.Length;
            self->playable = AnimationMixerPlayable.Create(graph, childrenCount);
            for (int i = 0; i < childrenCount; i++) {
                ref var clipLink = ref self->linkChildClips.GetRefAt(i);
                var animClip = clipLink.Get(buildContext);
                self->childPlayables[i] = AnimationClipPlayable.Create(graph, animClip);
                self->childPlayables[i].SetApplyFootIK(false);
                self->playable.ConnectInput(i, self->childPlayables[i], 0);
            }

            return self->playable;
        }

        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.EvaluateDelegate))]
        private static void Evaluate(in void* node, bool onEnter, in EvaluateContext evaluateContext, in GraphContext graphContext, ref UpstreamData upstream, ref ScopeContext scope) {
            var self = (BlendSpace2DNode*)node;

            int childrenCount = self->linkChildClips.Length;
            if (childrenCount == 0) return;
            if (childrenCount == 1) {
                self->timeState.Accumulate(evaluateContext.deltaTime);
                return;
            }

            NodeUtility.ClearWeights(ref self->weights, childrenCount);

            self->linkPosition.Evaluate(evaluateContext, graphContext, scope);
            float2 position = self->linkPosition.value;
            self->EvaluateWeight(position);
            self->NormalizeWeights();
            self->EvaluateTime(evaluateContext);
        }

        private void EvaluateWeight(in float2 inPosition) {
            ref var data = ref triangulationData;
            int triangleCount = data.triangles.Length;

            float2 p = (inPosition - data.gridMin) * data.gridScale;

            // if (triangleCount == 1) {
            //     var tri = data.triangles[0];
            //     int* clipId = tri.clipId;
            //
            //     if (clipId[0] == clipId[1])//若只有一个clip
            //     {
            //         weights[clipId[0]] = 1.0f;
            //         return;
            //     } else if (clipId[1] == clipId[2])//若只有两个clip
            //     {
            //         float2* vert = tri.vertices;
            //         var delta = vert[1] - vert[0];
            //         float alpha = math.dot((p - vert[0]), delta) / math.dot(delta, delta);
            //         if (alpha < 0) {
            //             weights[clipId[0]] = 1.0f;
            //         } else if (alpha > 1) {
            //             weights[clipId[1]] = 1.0f;
            //         } else {
            //             weights[clipId[0]] = 1.0f - alpha;
            //             weights[clipId[1]] = alpha;
            //         }
            //
            //         return;
            //     }
            // }

            if (lastTriangleIndex < 0 || lastTriangleIndex > triangleCount) {
                lastTriangleIndex = triangleCount / 2;
            }

            // 开始查找三角形
            var trianglesArrayPtr = (Triangle*)data.triangles.GetUnsafePtr();
            for (int attempt = 0; attempt < triangleCount; attempt++) {
                Triangle* tri = trianglesArrayPtr + lastTriangleIndex;
                float2* vert = tri->vertices;
                int* clipId = tri->clipId;
                int* edgeId = tri->edgeId;

                int largestEdgeIndex = -1;
                float largestEdgeDitance = 0; // 0.0001f; 输入为（45，0.99999…）时，点距边会小于0.0001f会导致判断三角形不准确，权重计算会变成负数，unity会将负权重识别为1
                for (int i = 0; i < 3; i++) {
                    float2 edgeStartPos = vert[i];
                    float distance = math.dot((p - edgeStartPos), data.edges[edgeId[i]].normal);

                    if (distance > largestEdgeDitance) {
                        largestEdgeDitance = distance;
                        largestEdgeIndex = i;
                    }
                }

                if (largestEdgeIndex < 0) {
                    float det = AnimMath.Cross(vert[1] - vert[0], vert[2] - vert[0]);
                    float w0 = AnimMath.Cross(vert[1] - p, vert[2] - p) / det;
                    float w1 = AnimMath.Cross(vert[2] - p, vert[0] - p) / det;
                    float w2 = 1 - w0 - w1;
                    weights[clipId[0]] += w0;
                    weights[clipId[1]] += w1;
                    weights[clipId[2]] += w2;
                    return;
                }

                int neighbourTriangleId = data.edges[edgeId[largestEdgeIndex]].neighbourTriangleId;
                if (neighbourTriangleId < 0) {
                    int origDir = -1;
                    int startIndex = largestEdgeIndex;
                    int endIndex = (startIndex + 1) % 3;
                    int prevTriID = lastTriangleIndex;
                    for (int attempt2 = 0; attempt2 < triangleCount; attempt2++) {
                        var edgeInfo = data.edges[edgeId[startIndex]];
                        float2 startPos = vert[startIndex];
                        float2 endPos = vert[endIndex];
                        float2 start2end = endPos - startPos;
                        float2 start2p = p - startPos;
                        float alpha = math.dot(start2p, start2end) / math.dot(start2end, start2end);

                        if (alpha >= 0 && alpha <= 1) {
                            weights[clipId[startIndex]] += 1.0f - alpha;
                            weights[clipId[endIndex]] += alpha;
                            return;
                        }

                        if (origDir == -1) {
                            origDir = alpha > 1 ? 1 : 0;
                        }

                        int dir = alpha > 1 ? 1 : 0;
                        if (origDir != dir) {
                            if (dir == 0)
                                weights[clipId[startIndex]] += 1.0f - alpha;
                            else
                                weights[clipId[endIndex]] += alpha;
                            lastTriangleIndex = prevTriID;
                            return;
                        }

                        prevTriID = lastTriangleIndex;
                        lastTriangleIndex = dir == 0 ? edgeInfo.adjacentPerimeterTriangleId.x : edgeInfo.adjacentPerimeterTriangleId.y;

                        if (lastTriangleIndex < 0) {
                            if (dir == 0)
                                weights[clipId[startIndex]] += 1.0f - alpha;
                            else
                                weights[clipId[endIndex]] += alpha;
                            lastTriangleIndex = prevTriID;
                            return;
                        }

                        tri = trianglesArrayPtr + lastTriangleIndex;
                        vert = tri->vertices;
                        clipId = tri->clipId;
                        edgeId = tri->edgeId;

                        startIndex = dir == 0 ? edgeInfo.adjacentPerimeterVertexId.x : edgeInfo.adjacentPerimeterVertexId.y;
                        endIndex = (startIndex + 1) % 3;
                    }
                } else {
                    lastTriangleIndex = neighbourTriangleId;
                }
            }
        }

        private void NormalizeWeights() {
            float w = 0;
            for (int i = 0; i < weights.Length; i++) {
                w += weights[i];
            }
            if (w == 0) return;
            if (w.IsNear(1)) return;
            w = 1 / w;
            for (int i = 0; i < weights.Length; i++) {
                weights[i] *= w;
            }
        }

        private void EvaluateTime(in EvaluateContext evaluateContext) {
            float duration = 0;
            for (int i = 0; i < linkChildClips.Length; i++) {
                duration += weights[i] * linkChildClips[i].duration;
            }
            timeState.SetDuration(duration);
            timeState.Accumulate(evaluateContext.deltaTime);
        }

        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.ApplyDelegate))]
        private static void Apply(in void* node) {
            var self = (BlendSpace2DNode*)node;

            int childCount = self->linkChildClips.Length;
            if (childCount == 0) return;
            if (childCount == 1) {
                self->playable.SetInputWeight(0, 1);
                self->childPlayables[0].SetTime(self->timeState.time);
                return;
            }

            float normalizedTime = self->timeState.time;
            for (int i = 0; i < self->childPlayables.Length; i++) {
                float w = self->weights[i];
                self->playable.SetInputWeight(i, w);

                self->childPlayables[i].SetTime(self->linkChildClips[i].duration * normalizedTime);
            }
        }

    }
}
